package org.dreamwork.tools.network.bridge.client.fx.controllers;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import netscape.javascript.JSObject;
import org.dreamwork.concurrent.broadcast.ILocalBroadcastReceiver;
import org.dreamwork.concurrent.broadcast.LocalMessage;
import org.dreamwork.network.service.ServiceFactory;
import org.dreamwork.tools.network.bridge.client.ManagerClient;
import org.dreamwork.tools.network.bridge.client.ProxyFactory;
import org.dreamwork.tools.network.bridge.client.concurrent.Const;
import org.dreamwork.tools.network.bridge.client.data.Proxy;
import org.dreamwork.tools.network.bridge.client.data.Server;
import org.dreamwork.tools.network.bridge.client.fx.DialogHelp;
import org.dreamwork.tools.network.bridge.client.fx.GuiContext;
import org.dreamwork.tools.network.bridge.client.fx.StageManager;
import org.dreamwork.tools.network.bridge.client.services.IBroadcastService;
import org.dreamwork.tools.network.bridge.client.services.IClientMonitorService;
import org.dreamwork.tools.network.bridge.client.services.IClientService;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeoutException;

import static org.dreamwork.tools.network.bridge.client.concurrent.Const.MessageType.*;

/**
 * Created by seth.yang on 2020/3/13
 */
public class MainController2 extends AbstractWebPage implements ILocalBroadcastReceiver {
    private final Logger logger = LoggerFactory.getLogger (MainController2.class);
    private final IClientService service = ServiceFactory.get (IClientService.class);
    private final IClientMonitorService monitor = ServiceFactory.get (IClientMonitorService.class);

    public MainController2 () {
        super ("web-ui/index.html");
        setImplicitExit (false);

        IBroadcastService service = ServiceFactory.get (IBroadcastService.class);
        service.register (Const.Action.ACT_TUNNELS, this);
    }

    private List<Map<String, Object>> getAllTunnels () {
        IClientService service = ServiceFactory.get (IClientService.class);
        List<Proxy> proxies = service.getProxies ();
        Map<String, Server> servers = new HashMap<> ();
        {
            List<Server> list = service.getServers ();
            if (list != null && !list.isEmpty ()) {
                for (Server server : list) {
                    servers.put (server.getName (), server);
                }
            }
        }
        List<Map<String, Object>> data = new ArrayList<> ();
        if (proxies != null && !proxies.isEmpty ()) {
            IClientMonitorService monitor = ServiceFactory.get (IClientMonitorService.class);
            for (Proxy p : proxies) {
                Map<String, Object> map = new HashMap<> ();
                map.put ("id", p.id);
                map.put ("name", p.name);
                map.put ("server", servers.get (p.server).getHost ());
                map.put ("service_port", p.servicePort);
                map.put ("peer", p.peer);
                map.put ("peer_port", p.peerPort);
                map.put ("state", monitor.containsClient (p.name) ? "connected":"disconnected");

                data.add (map);
            }
        }

        return data;
    }

    protected Object performAction (String action, Object params) {
        if (logger.isTraceEnabled ()) {
            logger.trace ("action = {}, params = {}", action, params);
        }

        try {
            if (!StringUtil.isEmpty (action)) {
                switch (action) {
                    case "btn-cloud-server":
                        StageManager.loadPage (new ServerManagerWebPage (), "tips.server.manage");
                        break;

                    case "btn-create":
                        StageManager.loadPage (new TunnelWebPage (), "title.tunnel.manage");
                        break;

                    case "btn-remove":
                        String tid = (String) params;
                        service.deleteProxyById (tid);
                        bridge.eval ("on_tunnel_deleted ('" + tid + "')");
                        return true;

                    case "btn-edit":
                        StageManager.loadPage (new TunnelWebPage ((String) params), "title.tunnel.manage");
                        break;

                    case "btn-connected":
                        background (()->connect ((String) params));
                        break;

                    case "btn-disconnected":
                        background (()->disconnect ((String) params));
                        break;

                    case "btn-settings":
                        break;

                    case "btn-about":
                    case "btn-help":
                        URL url = getClass ().getClassLoader ().getResource ("jfx/about.fxml");
                        if (url != null) {
                            FXMLLoader loader = new FXMLLoader (url);
                            loader.setResources (GuiContext.ui);
                            Parent root = loader.load ();
                            Scene scene = new Scene (root);
                            Stage stage = new Stage (StageStyle.TRANSPARENT);
                            stage.initModality (Modality.APPLICATION_MODAL);
                            stage.setScene (scene);
                            stage.setResizable (false);
                            ((AboutController) loader.getController ()).setStage (stage);
                            stage.showAndWait ();
                        }
                        break;

                    default:
                        logger.warn ("unknown action: {}", action);
                        break;
                }
            }
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);
        }
        return null;
    }

    private void connect (String proxyId) {
        Proxy proxy = service.getById (Proxy.class, proxyId);
        if (proxy != null) {
            Server server = service.get (proxy.server);
            try {
                ProxyFactory.connect (server, proxy);
            } catch (TimeoutException ex) {
                logger.warn (ex.getMessage (), ex);
                DialogHelp.error (ex.getMessage (), ex);
            }
        }
    }

    private void disconnect (String proxyId) {
        Proxy proxy = service.getById (Proxy.class, proxyId);
        if (proxy != null) {
            ManagerClient client = monitor.getClient (proxy.name);
            if (client != null) {
                client.detach ();
            }
        }
    }

    protected void onPageLoaded (JSObject window) {
        background (() -> {
            List<Map<String, Object>> tunnels = getAllTunnels ();
            Map<String, Object> data = new HashMap<> ();
            data.put ("tunnels", tunnels);

            if (logger.isTraceEnabled ()) {
                Gson g = new GsonBuilder ().setPrettyPrinting ().create ();
                logger.trace (g.toJson (data));
            }
            String param = new Gson ().toJson (data);
            bridge.firePageLoaded (param);
        });
    }

    @Override
    public void received (LocalMessage message) {
        if (message == null) {
            logger.trace ("null message");
        } else {
            post (() -> {
                Gson g = new Gson ();
                switch (message.what) {
                    case MT_TUNNEL_ADD:
                        logger.info ("a new tunnel add: {}", message.arg);
                        Proxy p = (Proxy) message.arg;
                        bridge.eval ("append_tunnel ('" + g.toJson (p) + "')");
                        break;

                    case MT_TUNNEL_DELETE:
                        logger.info ("a new tunnel delete: {}", message.arg);
                        bridge.eval ("on_tunnel_deleted ('" + message.arg + "')");
                        break;

                    case MT_TUNNEL_UPDATE:
                        logger.info ("a new tunnel update: {}", message.arg);
                        bridge.eval ("on_tunnel_update (" + g.toJson (message.arg) + ")");
                        break;

                    case MT_TUNNEL_CONNECTED:
                        logger.info ("connect[{}] connected", message.arg);
                        bridge.eval ("on_tunnel_connect_state_changed ('" + message.arg + "', 'connected')");
                        break;

                    case MT_TUNNEL_DISCONNECTED:
                        logger.info ("connect[{}] disconnected", message.arg);
                        bridge.eval ("on_tunnel_connect_state_changed ('" + message.arg + "', 'disconnected')");
                        break;

                    case MT_TUNNEL_CONN_FAILED:
                        logger.info ("connect[{}] disconnected", message.arg);
                        bridge.eval ("on_tunnel_connect_state_changed ('" + message.arg + "', 'failed')");
                        break;
                }
            });
        }
    }
}